Skip to content

Localization: stage translations into the String Catalog (manual)#25713

Open
jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/catalog-strings-translation
Open

Localization: stage translations into the String Catalog (manual)#25713
jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/catalog-strings-translation

Conversation

@jkmassel

@jkmassel jkmassel commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Fills the app's String Catalog (Localizable.xcstrings) with translations — the human translations we already have from GlotPress, plus machine translations for whatever is still untranslated. This gets the catalog ready ahead of moving the app over to String Catalogs. Nothing users see changes.

Two things to know

Staged, not shipped. The catalog file is generated, but the app doesn't read it at runtime yet — regular strings still come from the existing Localizable.strings. This only pre-fills the catalog for the eventual switch-over. Machine translations are marked needs_review, so they stay clearly separate from reviewed human translations and can sit in the catalog without shipping to anyone.

Manual, not automated. These lanes are run by hand, not wired into any release or CI step: a run extracts strings, calls a paid translation API, and commits a large file, so it shouldn't happen on its own. The unit tests still run in CI.

How it works

Three steps, each runnable on its own:

  1. generate_strings_catalog — extracts the English strings from the code into the catalog.
  2. download_catalog_strings — downloads the current translations from GlotPress.
  3. localize_catalog — adds the human translations, machine-translates whatever's left, and commits.

Each string gets the best translation available in each language: a human translation if there is one, otherwise a machine translation, otherwise the English text. Human translations are marked translated; machine translations and English fallbacks are marked needs_review.

Re-running is cheap. A machine translation already in the catalog is left alone instead of being requested again, and a human translation always replaces a machine one on the next run. Machine translation only happens when an API key is set — without one, the catalog fills from human translations and English. A machine translation is kept only if its format placeholders (%1$@, %2$d, …) match the English, so a mangled one falls back to English rather than shipping a broken string.

Test plan

  • Unit tests pass (fastlane/lanes/*_test.rb) — these run in CI
  • rubocop clean
  • Manual run against live GlotPress data (steps below)

Manual run

First bundle install. A machine-translation run also needs an ANTHROPIC_API_KEY — the Fastfile picks it up from ~/.wpios-env.default. Without a key the run still works: it fills in the human translations and English only, skips machine translation, and costs nothing — a good way to check the plumbing before spending anything.

Run the three lanes, scoped to one well-translated locale to keep it cheap:

bundle exec fastlane ios generate_strings_catalog
bundle exec fastlane ios download_catalog_strings locales:fr
bundle exec fastlane ios localize_catalog locales:fr
  1. generate_strings_catalog extracts the English strings into Localizable.xcstrings (~3,900 of them). No network, no cost.
  2. download_catalog_strings downloads the French translations from GlotPress into a scratch folder. No cost.
  3. localize_catalog fills the French translations into the catalog, machine-translates the gaps, and commits.

French is well-translated, so most entries come from GlotPress and only a small number fall to machine translation, which keeps the cost for one locale low. A few entries may stay English where a machine translation was rejected for not matching the English placeholders.

localize_catalog prints a per-locale summary at the end, so you can check the result straight from the log — no need to open the catalog:

fr: 3923 entries — 3805 human, 112 machine, 6 still English
    editor.post.delete: "Delete" → "Supprimer"
    settings.title: "Settings" → "Réglages"

Expect mostly human, a smaller machine count, and only a few still English. The sample lines let you spot-check the machine translations for obvious nonsense.

localize_catalog commits the catalog, which is a large file. This run is only to check the output and cost, so discard the commit rather than pushing it (git reset --hard HEAD~1). The catalog gets committed for real during the migration, not in this PR.

Plurals are handled the same way in a separate PR (#25710), since they live in their own catalog file.

@dangermattic

dangermattic commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

Base automatically changed from jkmassel/claude-string-translation to trunk June 30, 2026 23:07
@jkmassel jkmassel force-pushed the jkmassel/catalog-strings-translation branch from d5697f8 to a45aaf1 Compare July 1, 2026 01:27
@jkmassel jkmassel changed the title Localization: stage regular-string translations into Localizable.xcstrings (manual) Localization: stage translations into the String Catalog (manual) Jul 6, 2026
@jkmassel jkmassel force-pushed the jkmassel/catalog-strings-translation branch 3 times, most recently from b558570 to c5a43d9 Compare July 14, 2026 23:15
Adds the localize half of the String Catalog pipeline: a manual (non-CI) lane that folds current GlotPress translations for regular, non-plural strings into the existing Localizable.xcstrings as `human ?? AI ?? English`. The catalog isn't the runtime store yet (the app still ships Localizable.strings), so this only pre-populates it for the eventual cutover — nothing users see changes.

- `localize_catalog` lane: download current GlotPress translations into a throwaway dir, fold them in (human => `translated`), then AI-fill the rest (=> `needs_review`). Manual only — it calls the translation API (cost) and commits a large catalog; never wired into `download_localized_strings` or CI.
- `CatalogStrings.fold_translations!`: pure, reuse-aware fold. An existing valid cell is kept, so re-runs only translate genuinely-new gaps — the catalog's `needs_review` state IS the persistence (no side-store). A human translation from GlotPress supersedes a kept cell on the next fold.
- Immutable-key enforcement replaces `reconcile_changed_sources`: reworded English in place is now a hard error, since `xcstringstool sync` silently keeps the old key's translations and would ship stale text. Rewording requires a new key.
- `translate_all` keeps completed batches when one fails mid-run, so a partial API failure degrades to English for the remainder instead of dropping the lot.
- docs/localization-pipeline.md documents the regular-string fold and the `human ?? AI ?? English` floor — any translation, human or machine, that fails the placeholder gate falls through to the next rung.
@jkmassel jkmassel force-pushed the jkmassel/catalog-strings-translation branch from c5a43d9 to cd9b85f Compare July 16, 2026 18:22
@jkmassel jkmassel self-assigned this Jul 16, 2026
@jkmassel jkmassel added this to the 27.1 milestone Jul 16, 2026
@jkmassel jkmassel added Tooling Build, Release, and Validation Tools [Type] Enhancement labels Jul 16, 2026
@jkmassel jkmassel marked this pull request as ready for review July 16, 2026 18:48
@jkmassel jkmassel requested a review from a team as a code owner July 16, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Tooling Build, Release, and Validation Tools [Type] Enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants